package awesome.cpu.control;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

import awesome.cpu.gui.MainWindow;
import awesome.cpu.structure.ALU;
import awesome.cpu.structure.ALUOut;
import awesome.cpu.structure.BranchCondGates;
import awesome.cpu.structure.BusMonitor;
import awesome.cpu.structure.Component;
import awesome.cpu.structure.Control;
import awesome.cpu.structure.FlagRegister;
import awesome.cpu.structure.ImmShifter;
import awesome.cpu.structure.InstructionRegister;
import awesome.cpu.structure.Memory;
import awesome.cpu.structure.MemoryRegister;
import awesome.cpu.structure.Mux2PC;
import awesome.cpu.structure.Mux2WD1;
import awesome.cpu.structure.Mux2WR1;
import awesome.cpu.structure.MuxA;
import awesome.cpu.structure.MuxB;
import awesome.cpu.structure.MuxImm2Ex;
import awesome.cpu.structure.MuxPC2M;
import awesome.cpu.structure.OrGate;
import awesome.cpu.structure.PC;
import awesome.cpu.structure.PCRegister;
import awesome.cpu.structure.RegBShifter;
import awesome.cpu.structure.RegisterA;
import awesome.cpu.structure.RegisterB;
import awesome.cpu.structure.RegisterC;
import awesome.cpu.structure.RegisterFile;
import awesome.cpu.structure.SequentialComponent;
//import awesome.cpu.utils.Assembler;
//import awesome.cpu.utils.Mode;
import awesome.cpu.utils.Assembler;
import awesome.cpu.utils.Mode;

public class CentralProcessorUnit {
	
	List<Component> mComponents = new ArrayList<Component>();
	private boolean isRunning = false;
	private Semaphore semStop = new Semaphore(0);
	
	private final static CentralProcessorUnit mInstance = new CentralProcessorUnit();
	
	private CentralProcessorUnit() {}
	
	public static CentralProcessorUnit getInstance() {
		return mInstance;
	}
	
	public void start() {		
		isRunning = true;
		
		// prepare all components
		mComponents.clear();
		mComponents.add(ALU.getInstance());
		mComponents.add(ALUOut.getInstance());
		mComponents.add(BranchCondGates.getInstance());
		mComponents.add(Control.getInstance());
		mComponents.add(FlagRegister.getInstance());
		mComponents.add(ImmShifter.getInstance());
		mComponents.add(InstructionRegister.getInstance());
		mComponents.add(Memory.getInstance());
		mComponents.add(MemoryRegister.getInstance());
		mComponents.add(Mux2PC.getInstance());
		mComponents.add(Mux2WD1.getInstance());
		mComponents.add(Mux2WR1.getInstance());
		mComponents.add(MuxA.getInstance());
		mComponents.add(MuxB.getInstance());
		mComponents.add(MuxImm2Ex.getInstance());
		mComponents.add(MuxPC2M.getInstance());
		mComponents.add(OrGate.getInstance());
		mComponents.add(PC.getInstance());
		mComponents.add(RegBShifter.getInstance());
		mComponents.add(RegisterA.getInstance());
		mComponents.add(RegisterB.getInstance());
		mComponents.add(RegisterC.getInstance());
		mComponents.add(RegisterFile.getInstance());
		mComponents.add(PCRegister.getInstance());

		// load program
		byte[] instructions = Assembler.asmToCodeBySrc
				(MainWindow.getInstance().getInputSrcCode());
		if (instructions != null) {
			Memory.getInstance().loadProgram(instructions);
		} else {
			if (Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ERR) {
				System.err.println("Assembler failed!");
			}
		} 

		// launch bus monitor
		BusMonitor.getInstance().start();
		
		// launch all components
		for (Component c : mComponents) {
			if (c instanceof SequentialComponent) {
				((SequentialComponent)c).start();
			}
		}

		// prepare clock
		Clock.getInstance().start();
		
		new Thread (){
			public void run(){
				try {
					semStop.acquire();
					CentralProcessorUnit.this.stop();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
	}
	
	public void makeStop() {
		if (isRunning) {
			semStop.release();
			if (Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL) {
				System.out.println("released");
			}
		}
	}
	
	private void stop() {
		isRunning = false;

		// stop clock
		Clock.getInstance().stopRunning();
		
		// stop all components
		for (Component c : mComponents) {
			if (c instanceof SequentialComponent) {
				((SequentialComponent)c).stop();
			} else if (c instanceof Control) {
				((Control)c).stop();
			}
		}
		
		// stop bus monitor
		BusMonitor.getInstance().stop();
		
		synchronized (Clock.getInstance()) {
			Clock.getInstance().notifyAll();
		}
		synchronized (BusMonitor.getInstance()) {
			BusMonitor.getInstance().notifyAll();
		}
	}
	
	public boolean isRunning() {
		return isRunning;
	}
}





